﻿/********************************************************************************
* Copyright 2010 Zane Thorn (zane.thorn@gmail.com)                              *
*                                                                               *
* NeturalMath is free software: you can redistribute it and/or modify           *
* it under the terms of the GNU Lesser General Public License as published by   *
* the Free Software Foundation, either version 3 of the License, or             *
* (at your option) any later version.                                           *
*                                                                               *
* NeturalMath is distributed in the hope that it will be useful,                *
* but WITHOUT ANY WARRANTY; without even the implied warranty of                *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
* GNU Lesser General Public License for more details.                           *
*                                                                               *
* You should have received a copy of the GNU Lesser General Public License      *
* along with NeturalMath.  If not, see <http://www.gnu.org/licenses/>.          *
********************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NeturalMath.Properties;

namespace NeturalMath
{
    /// <summary>
    /// Build in data type to represent a mathematical range of values
    /// </summary>
    public sealed class RangeValue:MathValue,IEnumerable<MathValue>
    {
        #region Constructors

        /// <summary>
        /// Creates a new instance of the range class
        /// </summary>
        /// <param name="lower"></param>
        /// <param name="upper"></param>
        internal RangeValue(NumberValue lower, NumberValue upper,MathRuntime runtime) 
            : base(ValueTypes.Range, "range",new Tuple<double,double>(lower.Value,upper.Value),runtime)
        {
            if (lower == Runtime.Undefined || upper == Runtime.Undefined)
                throw new MathException(ErrorCodes.RangeCannotHaveUndefinedParameters,
                                        "Range cannot have undefined parameters");

            Lower = lower;
            Upper = upper;
            Spread = (NumberValue)(upper - lower);

            // calcuate absolute value
            var b = lower.Value * Spread.Value;
            var top = Spread.Value * Spread.Value / 2;

            Value = (NumberValue)Runtime.NewNumber(b + top);
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// The lower bounds for the range
        /// </summary>
        public NumberValue Lower { get; private set; }

        /// <summary>
        /// The upper bounds for the range
        /// </summary>
        public NumberValue Upper { get; private set; }

        public NumberValue Spread { get; private set; }

        public new NumberValue Value { get; private set; }

        #endregion

        #region Public Methods

        public MathValue Overlaps(RangeValue other)
        {
            return ((other.Lower >= Lower && other.Lower <= Upper) 
                || (other.Upper >= Lower && other.Upper <= Upper)
                   );
        }

        public double AbsDifference(RangeValue other)
        {
            // get base values
            var la = Lower.Value;
            var lb = other.Lower.Value;
            var ua = Upper.Value;
            var ub = other.Upper.Value;
            
            // get diffs
            var da = ua - la;
            var db = ub - lb;
            var x = la - lb;

            // find abs difference
            return (x*db) + (((x*da*db) - (x*da) - (da*db) - db)/2);
        }

        #endregion

        #region Helper Methods

        #region Basic Math Functions

        /// <summary>
        /// Adds two values together
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Add(MathValue arg)
        {
            var other = (RangeValue)arg;
            return Runtime.NewRange((NumberValue)(Lower + other.Lower), (NumberValue)(Upper + other.Upper));
        }

        /// <summary>
        /// Subtracts the argument from the present value
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Subtract(MathValue arg)
        {
            var other = (RangeValue)arg;
            return Runtime.NewRange((NumberValue)(Lower - other.Lower), (NumberValue)(Upper - other.Upper));
        }

        /// <summary>
        /// Multiplies two values together
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Multiply(MathValue arg)
        {
            var other = (RangeValue)arg;
            return Runtime.NewRange((NumberValue)(Lower * other.Lower), (NumberValue)(Upper * other.Upper));
        }

        /// <summary>
        /// Divides two values
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Divide(MathValue arg)
        {
            var other = (RangeValue)arg;
            if (other.Lower == Runtime.Zero || other.Upper == Runtime.Zero)
                return Runtime.Undefined;

            return Runtime.NewRange((NumberValue)(Lower / other.Lower), (NumberValue)(Upper / other.Upper));
        }

        /// <summary>
        /// Performs modulus division on two values
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Modulo(MathValue arg)
        {
            var other = (RangeValue)arg;
            if (other.Lower == Runtime.Zero || other.Upper == Runtime.Zero)
                return Runtime.Undefined;

            return Runtime.NewRange((NumberValue)(Lower % other.Lower), (NumberValue)(Upper % other.Upper));
        }

        /// <summary>
        /// Raises the value to the power provided
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Power(MathValue arg)
        {
            var other = (RangeValue)arg;
            return Runtime.NewRange((NumberValue)(PowerOperator(Lower , other.Lower)), (NumberValue)(PowerOperator(Upper, other.Upper)));
        }

        #endregion

        #region Logical Functions

        /// <summary>
        /// Performs an AND operation on the value
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue And(MathValue arg)
        {
            var other = (RangeValue)arg.ConvertToRange();
            var overlaps = Overlaps(other);
            if (!(bool)overlaps.Value)
                return Runtime.Void;

            var biggestLower = other.Lower > Lower ? other.Lower : Lower;
            var smallestUpper = other.Upper < Upper ? other.Upper : Upper;

            return Runtime.NewRange(biggestLower, smallestUpper);
        }

        /// <summary>
        /// Performs an OR operation on the value
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Or(MathValue arg)
        {
            var other = (RangeValue)arg.ConvertToRange();
            var overlaps = Overlaps(other);
            if (!(bool)overlaps.Value)
                return Runtime.Void;

            var smallestLower = (other.Lower < Lower) ? other.Lower : Lower;
            var biggestUpper = (other.Upper > Upper) ? other.Upper : Upper;

            return Runtime.NewRange(smallestLower, biggestUpper);
        }

        /// <summary>
        /// Performsn an XOR operaton on the values
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue Xor(MathValue arg)
        {
            var other = (RangeValue)arg.ConvertToRange();
            var overlaps = Overlaps(other);
            if (!(bool)overlaps.Value)
            {
                var lower = (this < other) ? this : other;
                var upper = (this < other) ? other : this;
                return Runtime.NewSet(lower, upper);
            }

            var lowestLower = (other.Lower < Lower) ? other.Lower : Lower;
            var biggestLower = (other.Lower < Lower) ? Lower : other.Lower;

            var lowestUpper = (other.Upper > Upper) ? Upper : other.Upper;
            var biggestUpper = (other.Upper > Upper) ? other.Upper : Upper;

            return Runtime.NewSet(
                Runtime.NewRange(lowestLower, biggestLower),
                Runtime.NewRange(lowestUpper, biggestUpper)
                );
        }

        #endregion

        #region Relational Functions

        /// <summary>
        /// Determines if this value is less than the argument provided
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override bool? IsLessThan(MathValue arg)
        {
            var other = arg.ConvertToRange() as RangeValue;
            if (other==null)
                return null;
            if (Upper < other.Lower)
                return true;
            if (Lower > other.Upper)
                return false;
            return null;
        }


        /// <summary>
        /// Determines if the value is greater than  to the value provided
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override bool? IsGreaterThan(MathValue arg)
        {
            var other = arg.ConvertToRange() as RangeValue;
            if (other == null)
                return null;
            if (Upper < other.Lower)
                return false;
            if (Lower > other.Upper)
                return true;
            return null;
        }


        #endregion

        #region Conversion Functions

        /// <summary>
        /// Flips the upper and lower ranges of the value
        /// </summary>
        /// <returns></returns>
        protected override MathValue Flip()
        {
            return Runtime.NewRange((NumberValue)~Lower, (NumberValue)~Upper);
        }

        /// <summary>
        /// Attempts to convert the value to true
        /// </summary>
        /// <returns></returns>
        protected override bool IsTrue()
        {
            return (bool) Value.ConvertToBoolean().Value;
        }


        /// <summary>
        /// Attempts to convert the value to a negative
        /// </summary>
        /// <returns></returns>
        protected override MathValue ToMinus()
        {
            return Runtime.NewRange(-Lower.Value, -Upper.Value);
        }

        /// <summary>
        /// Converts the current value to a string
        /// </summary>
        /// <returns>The string value of the data</returns>
        protected internal override MathValue ConvertToString()
        {
            return Runtime.NewString(GetToString());
        }

        /// <summary>
        /// Converts a current value to a number
        /// </summary>
        /// <returns>The numeric value of the data</returns>
        protected internal override MathValue ConvertToNumber()
        {
            return Value;
        }

        /// <summary>
        /// Converts the current value to a boolean
        /// </summary>
        /// <returns></returns>
        protected internal override MathValue ConvertToBoolean()
        {
            return Value.ConvertToBoolean();
        }

        /// <summary>
        /// Converts the argument into the present data type
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected override MathValue ConvertToNativeValue(MathValue arg)
        {
            return arg.ConvertToRange();
        }

        /// <summary>
        /// Converts the value to a range
        /// </summary>
        /// <returns></returns>
        protected internal override MathValue ConvertToRange()
        {
            return this;
        }

        /// <summary>
        /// Converts the value to a complex
        /// </summary>
        /// <returns></returns>
        protected internal override MathValue ConvertToComplex()
        {
            return Runtime.NewComplex(Value);
        }

        /// <summary>
        /// Converts the value to a unit, based on the specified unit measure
        /// </summary>
        /// <param name="measure"></param>
        /// <returns></returns>
        protected internal override MathValue ConvertToUnit(Units.UnitMeasure measure)
        {
            return Runtime.NewUnit(Value, measure);
        }

        #endregion

        /// <summary>
        /// Gets a string representation of the data
        /// </summary>
        /// <returns></returns>
        protected override string GetToString()
        {
            return Lower + "->" + Upper;
        }

        #endregion

        #region Enumerator

        private class RangeValueEnumerator:IEnumerator<MathValue>
        {
            #region Constructors

            public RangeValueEnumerator(RangeValue source)
            {
                Source = source;
                Current = source.Lower;
            }

            #endregion

            #region Public Properties

            public RangeValue Source { get; private set; }

            #endregion

            #region IEnumerator<MathValue> Members

            public MathValue Current { get; private set; }

            #endregion

            #region IDisposable Members

            public void Dispose()
            {
                // does nothing
            }

            #endregion

            #region IEnumerator Members

            object System.Collections.IEnumerator.Current
            {
                get { return this.Current; }
            }

            public bool MoveNext()
            {
                Current++;
                return (bool)(Current <= Source.Upper).Value;
            }

            public void Reset()
            {
                Current = Source.Lower;
            }

            #endregion
        }

        #endregion

        #region IEnumerable<MathValue> Members

        public IEnumerator<MathValue> GetEnumerator()
        {
            return new RangeValueEnumerator(this);
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return new RangeValueEnumerator(this);
        }

        #endregion
    }
}
